<?php

/**
 * mForge Framework
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2011, Jonathan Hedrén
 * @author     Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

namespace mForge;

/**
 * The Request represents either an HTTP request or a internally forwarded
 * request.
 */
class Request
{
    /**
     * The base url part of the request URI.
     *
     * @var string
     */
    private $applicationPath;

    /**
     * The original path info.
     *
     * @var string
     */
    protected $originalPathInfo;

    /**
     * The parameters of the request. Can be both /-separated parameters and/or
     * $_GET parameters.
     *
     * @var array
     */
    private $params = array();

    /**
     * The the original path info minus the application path ($applicationPath)
     *
     * @var string
     */
    private $pathInfo;

    /**
     * POST parameters
     *
     * @var array
     */
    private $post;

    /**
     * The full request URI
     *
     * @var string
     */
    private $uri;

    /**
     * Constructor.
     *
     * @return Request
     */
    public function __construct()
    {
        if ($this->getOriginalPathInfo() === null) {
            throw new \Exception('An enabled rewrite engine is required for this system.');
            exit;
        }

        $this->setUri()
             ->setApplicationPath()
             ->setPathInfo();

        $this->params = $_GET;

        return $this;
    }

    /**
     * Returns the application path.
     *
     * @return string
     */
    public function getApplicationPath()
    {
        return $this->applicationPath;
    }

    /**
     * Gets a single entry from the $_FILES array.
     *
     * @param string $key
     * @return array|null
     */
    public function getFile($key) {
        if ($this->getFiles() !== null) {
            return isset($_FILES[$key]) ? $_FILES[$key] : null;
        }
        return null;
    }

    /**
     * Gets the $_FILES array or null if it is empty.
     *
     * @return array|null
     */
    public function getFiles()
    {
        return (is_array($_FILES) && count($_FILES) > 0) ? $_FILES : null;
    }

    /**
     * Gets the HTTP host.
     *
     * @return string|null
     */
    public function getHost()
    {
        return isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
    }

    /**
     * Returns the HTTP method (i.e. GET, HEAD, POST, PUT or DELETE);
     *
     * @return string
     */
    public function getMethod()
    {
        return $_SERVER['REQUEST_METHOD'];
    }

    /**
     * Gets the original path info.
     *
     * @return string
     */
    public function getOriginalPathInfo()
    {
        if ($this->originalPathInfo === null) {
            if (isset($_SERVER['HTTP_X_ORIGINAL_URL'])) {
                $this->originalPathInfo = $_SERVER['HTTP_X_ORIGINAL_URL'];
            } else if (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
                $this->originalPathInfo = $_SERVER['HTTP_X_REWRITE_URL'];
            } else if (isset($_SERVER['ORIG_PATH_INFO'])) {
                $this->originalPathInfo = $_SERVER['ORIG_PATH_INFO'];
            } else if (isset($_SERVER['REQUEST_URI'])) {
                $this->originalPathInfo = $_SERVER['REQUEST_URI'];
            }
        }
        return $this->originalPathInfo;
    }

    /**
     * Gets a request parameter by $key. If the param key not is set, the
     * $default value will be returned.
     *
     * @return string
     * @param mixed $default
     * @param string|integer $key
     */
    public function getParam($key, $default = null)
    {
        if (is_integer($key)) {
            $i = 0;
            foreach ($this->params as $evenParam => $oddParam) {
                if ($key == $i++) {
                    return $evenParam;
                }
                if ($key == $i++) {
                    return $oddParam;
                }
            }
        } else if (isset($this->params[$key])) {
            return $this->params[$key];
        }
        return $default;
    }

    /**
     * Returns the request parameters.
     *
     * @return array
     */
    public function getParams()
    {
        return $this->params;
    }

    /**
     * Gets the path info.
     *
     * @return string
     */
    public function getPathInfo()
    {
        return $this->pathInfo;
    }

    /**
     * Returns the physical path (i.e. the absolute file system path) of the
     * application.
     *
     * @return string
     */
    public function getPhysicalApplicationPath()
    {
        return dirname(realpath($_SERVER['SCRIPT_FILENAME']));
    }

    /**
     * Returns the port of the request.
     *
     * @return integer
     */
    public function getPort()
    {
        return isset($_SERVER['SERVER_PORT']) ? (integer) $_SERVER['SERVER_PORT'] : 80;
    }

    /**
     * Returns post ($_POST) variables as an associative array. A single value
     * will be returned if the parameter $key is passed. If the paramer key not
     * is set, the $default value will be returned.
     *
     * @param string|null $key
     * @param mixed $default
     * @return string
     */
    public function getPost($key = null, $default = null)
    {
        if ($this->post === null) {
            $post = array();
            foreach ($_POST as $postKey => $value) {
                if ($value === '') {
                    $value = null;
                }
                $post[$postKey] = $value;
            }
            $this->post = $post;
        }

        if ($key === null) {
            return $this->post;
        }

        return isset($this->post[$key]) ? $this->post[$key] : $default;
    }

    /**
     * Returns the protocol of the request (i.e. http or https).
     *
     * @return string
     */
    public function getProtocol()
    {
        return (isset($_SERVER['HTTPS'])
            && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    }

    /**
     * Gets the referer.
     *
     * @return string
     */
    public function getReferer()
    {
        return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
    }

    /**
     * Returns the request time.
     *
     * @return integer
     */
    public function getRequestTime()
    {
        return isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : null;
    }

    /**
     * Returns the query string (i.e. the part of the request URL that is after
     * the question mark)
     *
     * @return string
     */
    public function getQueryString()
    {
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : null;
    }

    /**
     * Returns the request URI.
     *
     * @return string
     */
    public function getUri()
    {
        return $this->uri;
    }

    /**
     * Returns the user agent.
     *
     * @return string
     */
    public function getUserAgent()
    {
        return $_SERVER['HTTP_USER_AGENT'];
    }

    /**
     * Gets the (proxy) ip of the visitor.
     *
     * @return string
     */
    public function getVisitorIp()
    {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
        } else if (isset($_SERVER['REMOTE_ADDR'])) {
            return $_SERVER['REMOTE_ADDR'];
        }
        return null;
    }

    /**
     * Checks if the request is an ajax/XMLHttpRequest.
     *
     * @return boolean
     */
    public function isAjax()
    {
        return (isset($_SERVER['HTTP_X_REQUESTED_WITH'])
            && ($_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest'));
    }

    /**
     * Returns true if the request is of POST type.
     *
     * @return boolean
     */
    public function isPost()
    {
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            return true;
        }
        return false;
    }

    /**
     * Redirects the request to some other URL.
     *
     * @param string $url
     * @return void
     * @todo Add possibility to pass a $params array containing key => value
     *       pairs with parameters that will be appended to the url.
     */
    public function redirectTo($url)
    {
        if (!preg_match('#^(https?)://#', $url)) {

            $host  = (string) $this->getHost();
            $protocol = $this->getProtocol();
            $port  = $this->getPort();
            $uri   = $protocol . '://' . $host;

            if ((('http' == $protocol) && (80 != $port))
                || (('https' == $protocol) && (443 != $port))) {
                $uri .= ':' . $port;
            }

            // If the redirect URL doesn't start with a /, we assume that it is
            // a 'relative' redirect and therefore prepend the application path.
            if (strpos($url, '/') !== 0) {
                $url = $this->applicationPath . '/' . $url;
            }

            $url = $uri . '/' . ltrim($url, '/');
        }

        header('HTTP/1.1 302 Found', true, 302);
        header('Status: HTTP/1.1 302 Found');
        header('Location: ' . $url);

        exit;
    }

    /**
     * Sets the application path.
     *
     * @param string $applicationPath
     * @return Request
     */
    private function setApplicationPath()
    {
        if (isset($_SERVER['SCRIPT_NAME'])) {
            $applicationPath = $_SERVER['SCRIPT_NAME'];
        } else {
            $applicationPath = substr_replace(
                $_SERVER['SCRIPT_FILENAME'],
                '',
                0,
                strlen($_SERVER['DOCUMENT_ROOT'])
            );
        }
        $applicationPath = dirname($applicationPath);
        $applicationPath = rtrim($applicationPath, '/');
        $this->applicationPath = $applicationPath;
        return $this;
    }

    /**
     * Sets the request parameters.
     *
     * @param array $params
     * @return Request
     */
    public function setParams(array $params = null)
    {
        $this->params = $params;
        return $this;
    }

    /**
     * Sets the path info. If the $pathInfo parameter is omitted, the path info
     * is set to the part of the request URI after the base URL.
     *
     * @return Request
     */
    private function setPathInfo()
    {
        $pathInfo = $this->getOriginalPathInfo();
        $queryStringStart = strpos($pathInfo, '?');
        if ($queryStringStart !== false) {
            $pathInfo = substr($pathInfo, 0, $queryStringStart);
        }
        if ($this->applicationPath !== ''
            && strpos(strtolower($pathInfo), strtolower($this->applicationPath)) === 0) {
                $pathInfo = substr(
                    ltrim($pathInfo, '/'),
                    strlen(ltrim($this->applicationPath, '/'))
                );
        }
        $pathInfo = trim($pathInfo, '/');

        $this->pathInfo = $pathInfo;

        return $this;
    }

    /**
     * Sets the request URI. If the $requestUri parameter is omitted, the
     * request URI will be taken from any available "rewrite engine" $_SERVER
     * parameter.
     *
     * @return Request
     */
    private function setUri()
    {
        $uri = $this->getProtocol() . '://' . $this->getHost();
        if ($this->getPort() !== 80) {
            $uri .= ':' .$this->getPort();
        }
        $uri .= urldecode($this->getOriginalPathInfo());

        $this->uri = $uri;

        return $this;
    }
}